package com.stormragetech.arthas.common.util;

import com.stormragetech.arthas.common.annotation.Action;
import com.stormragetech.arthas.common.constant.Constant;
import com.stormragetech.arthas.common.exception.ArthasException;
import lombok.extern.log4j.Log4j2;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * description: //TODO
 *
 * @author: Xue Bin
 * @version: 1.0.0-SNAPSHOT
 */
@Log4j2
public class ArthasUtil {

    public static Date strToDate(String source, String format) {

        DateFormat df = new SimpleDateFormat(format);
        try {
            return df.parse(source);
        } catch (ParseException e) {
            log.error("convert string to date is failed, exception is [ {} ]", e.getMessage());
            return null;
        }
    }

    public static void findFilesInPackage(String packageName, List<String> classes) {

        String packageDirName = packageDir(packageName);
        if (null == packageDirName) {
            // TODO
            return;
        }

        if (packageDirName.contains("jar!")) {
            String[] names = packageDirName.trim().substring(5).split("!");
            try {
                JarFile jar = new JarFile(names[0].trim());
                Enumeration<JarEntry> entries = jar.entries();
                while (entries.hasMoreElements()) {
                    JarEntry entry = entries.nextElement();
                    String fileName = entry.getName().replace("/", ".").trim();
                    if (fileName.contains(packageName) && fileName.contains(".class")) {
                        try {
                            Class c = Class.forName(fileName.substring(0, fileName.length() - 6));
                            if (null != c.getAnnotation(Action.class)) {
                                classes.add(fileName.substring(0, fileName.length() - 6));
                            }
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            File dir = new File(packageDirName);
            if (!dir.exists() || !dir.isDirectory()) {
                return;
            }

            File[] files = dir.listFiles((File file) -> {
                if (file.isDirectory())
                    return true;
                if (file.isFile()) {
                    String fileName = file.getName();
                    if (fileName.endsWith(".class")) {
                        try {
                            Class c = Class.forName(packageName + "." + fileName.substring(0, fileName.length() - 6));
                            if (null != c.getAnnotation(Action.class)) {
                                return true;
                            }
                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                }
                return false;
            });

            if (null == files) {
                return;
            }

            for (File file : files) {
                if (file.isDirectory()) {
                    findFilesInPackage(packageName + "." + file.getName(), classes);
                }
                if (file.isFile()) {
                    classes.add(packageName + "." + file.getName().substring(0, file.getName().length() - 6));
                }
            }
        }
    }

    private static String packageDir(String packageName) {

        if (null == packageName) {
            // TODO
            return null;
        }

        URL url = Thread.currentThread().getContextClassLoader().getResource(packageName.replace(".", "/"));
        log.debug("the url is {}", url);
        if (url == null || url.getFile() == null) {
            // TODO
            return null;
        }
        try {
            String path;
            if (System.getProperty("os.name").toLowerCase().contains("window")) {
                path = url.getPath().substring(1, url.getPath().length());
            } else {
                path = url.getPath();
            }
            return URLDecoder.decode(path, "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    public static InputStream parse(String name) {

        InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(name);
        return stream;
    }

    public static int[] parseServerParameter(String name) {

        try {
            Document document = new SAXReader().read(parse(name));
            Element element = document.getRootElement().element(Constant.ARTHAS_SERVER);
            String count = element.attributeValue(Constant.SERVER_THREAD_COUNT);
            String port = element.attributeValue(Constant.SERVER_PROT);
            return new int[]{null == count ? Constant.DEFAULT_THREAD_COUNT : Integer.valueOf(count), null == port ? Constant.DEFAULT_PORT : Integer.valueOf(port)};
        } catch (Exception e) {
            return new int[]{Constant.DEFAULT_THREAD_COUNT, Constant.DEFAULT_PORT,};
        }
    }

    public static List<String> parsePackage(String name) {

        List<String> namespaces = new ArrayList<>();

        Document document;
        try {
            document = new SAXReader().read(parse(name));
            if (null == document) {
                return namespaces;
            }
            Element root = document.getRootElement();
            Iterator<Element> it = root.elementIterator();
            while (it.hasNext()) {
                Element element = it.next();
                if (element.getName().equals(Constant.ACTION_ELEMENT)) {
                    namespaces.add(element.attributeValue(Constant.BASE_PACKAGE_ATTRIBUTE));
                }
            }
            return namespaces;
        } catch (DocumentException e) {
            e.printStackTrace();
            // TODO: to add error handle
            return namespaces;
        }
    }

    public static Map<String, String> paramsInUri(String uri) throws ArthasException {
        if (null == uri || uri.length() <= 0) {
            return null;
        }
        String params = uriSplit(uri);
        if (null == params) {
            return null;
        }
        return paramSplit(params);
    }

    private static String uriSplit(String uri) throws ArthasException {
        String[] arr = uri.split("\\?");
        if (arr.length != 2) {
            // TODO: discribe exception
            throw new ArthasException("123", "123", ArthasException.APP_ERR);
        }
        return arr[1];
    }

    private static Map<String, String> paramSplit(String params) {
        Map<String, String> map = new HashMap<>();
        String[] arr = params.split("&");
        Arrays.stream(arr).forEach((String param) -> {
            String[] pa = param.split("=");
            if (pa.length == 2) {
                map.put(pa[0], pa[1]);
            }
        });
        return map;
    }
}
